package com.nc.mem.serial.graph.common;

import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Comparator;
import java.util.SortedSet;

import com.nc.mem.serial.Serializer;
import com.nc.mem.serial.graph.Context;
import com.nc.mem.serial.graph.GraphSerializer;
import com.nc.mem.serial.graph.Representation;
import com.nc.mem.serial.graph.asm.GraphSerializerFactory;
import com.nc.mem.serial.graph.container.CollectionRegistry;


/**
 * Format is [colId+tag][colSize][data] where [data] = ([obj1Id +
 * tag][obj1ClassId][obj1Data])(....)([objNId + tag][objNClassId])
 * 
 * @author cmuramoto
 */
public final class CollectionSerializer extends GraphSerializer {

	public static CollectionSerializer INSTANCE = new CollectionSerializer();

	public CollectionSerializer() {
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doRead(final ByteBuffer src, final Object obj) {
		final Context current = Context.current();
		final Collection<Object> ret = (Collection<Object>) obj;
		final int sz = (int) (Representation.readLong(src) >> 1);

		if (sz > 0) {
			/*
			 * Cached inline: most collections won't actually use polymorphic references, so we only
			 * need to perform a single lookup on the serializers dictionary.
			 */
			Class<?> compClazz = null;
			Serializer ser = null;
			Serializer curr;
			int s;
			for (int i = 0; i < sz; i++) {
				s = Representation.readInt(src);
				if (s == 1) {
					ret.add(null);
				} else if ((s & 1 << 1) != 0) {
					ret.add(current.fromId(s));
				} else {
					final Class<?> currClazz = Representation.idToClass(src);
					if (compClazz == null) {
						compClazz = currClazz;
						ser = GraphSerializerFactory.serializer(currClazz);
					}

					curr = currClazz == compClazz ? ser : GraphSerializerFactory.serializer(currClazz);

					final Object o = curr.read(src, s);
					ret.add(o);
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doWrite(final ByteBuffer dest, final Object obj) {
		final Collection<Object> objs = (Collection<Object>) obj;
		Representation.writeClassId(dest, objs.getClass());

		final Comparator<?> c = objs instanceof SortedSet ? ((SortedSet<?>) objs).comparator() : null;
		final long s = objs.size() << 1 | (c != null ? 1 : 0);

		Representation.writeLong(dest, s);

		if (c != null) {
			GraphSerializerFactory.serializer(c.getClass()).write(dest, c);
			Representation.writeLong(dest, s);
		}

		/*
		 * Cached inline: most collections won't actually use polymorphic references, so we only
		 * need to perform a single lookup on the serializers dictionary.
		 */
		Class<?> compClazz = null;
		Serializer ser = null;
		Serializer curr;
		for (final Object o : objs) {
			if (o == null) {
				Representation.writeInt(dest, 1);
			} else {
				if (compClazz == null) {
					compClazz = o.getClass();
					ser = GraphSerializerFactory.serializer(compClazz);
				}
				curr = o.getClass() == compClazz ? ser : GraphSerializerFactory.serializer(o.getClass());
				curr.write(dest, o);
			}
		}
	}

	@Override
	public final Object instantiate(final ByteBuffer src) {
		final int id = Representation.readInt(src);
		final int p = src.position();
		final long szAndKind = Representation.readLong(src);

		Object ret;

		if ((szAndKind & 1) != 0) {
			final int s = Representation.readInt(src);

			Comparator<?> c = (Comparator<?>) Context.current().fromId(s);

			if (c == null) {
				c = (Comparator<?>) GraphSerializerFactory.serializer(Representation.idToClass(src)).read(src, s);
			}

			ret = CollectionRegistry.INSTANCE.newCollection(id, c);
		} else {
			src.position(p);
			ret = CollectionRegistry.INSTANCE.newCollection(id, (int) (szAndKind >> 1));
		}

		return ret;
	}
}